package FlowGram.Application.Parser.Prototype;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.regex.Matcher;
import FlowGram.Application.Parser.FinchExecutionLogger;
import FlowGram.Application.Parser.FinchParser;
import FlowGram.Application.Parser.FinchProgramFlow;
import FlowGram.Application.Parser.SystemOutExecutionLogger;
import FlowGram.Application.Parser.Controllers.FinchController;
import FlowGram.Application.Parser.Controllers.FinchRobotController;
import FlowGram.Application.Parser.Utility.ClassFinder;
import FlowGram.Application.Parser.Utility.Configuration;
import FlowGram.Application.Parser.Utility.ReadWriteTextFile;


/**
 * 
 * @author thackray
 *
 */
public class FinchParserPrototype {

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		Configuration.setDefault("config.xml");
		
		ArrayList<FinchController> controllers = loadControllers();
		
		System.out.println("Welcome to the Finch Interpreter.");
		
		Scanner menuScanner = new Scanner(System.in);
		while(true){
			
			System.out.println("Select an option below:");
			System.out.println("\t1. Run in interactive mode");
			System.out.println("\t2. Run a Finch code file");
			System.out.println("\t3. Attach Finch Robot");
			System.out.println("\t4. Exit");
			System.out.println();
			System.out.println("Enter your choice (1-4): ");			
			
			int option;
			try{
				option = Integer.parseInt(menuScanner.next());
			}catch(NumberFormatException e){
				System.out.println("Invalid choice. Please enter an Integer between 1 and 4.");
				continue;
			}
			if(option > 0 && option < 5){
				if(option == 4){
					break; //exit the main program loop
				}else if(option == 3){
					try {
						controllers.add(new FinchRobotController());
					} catch (Exception e) {
						System.out.println("Unable to attach to Finch. Ensure the finch is connected.\n"+e.getMessage());
					}
					continue;
				}else if(option == 1){
					System.out.println("\nEntering interactive mode.");
					System.out.println(" -> Type 'exit' to leave, 'commands' for a list of command, or 'startrecording'to record your commands.");
					Scanner scanner = new Scanner(System.in);
					FinchParser parser = new FinchParser();
					FinchExecutionLogger logger = new SystemOutExecutionLogger();
					((SystemOutExecutionLogger)logger).setPrefix("  ");
					boolean isRecording = false;
					String record = new String();
					while(true){
						boolean playingRecoring = false;
						System.out.print("> ");
						String input = scanner.nextLine();
						if(input.equals("exit")){
							break;
						}else if(input.equals("commands")){
							printActions();
							continue;
						}else if(input.equals("reset")){
							System.out.println("Reseting state of finch.");
							for(int i=0; i<controllers.size(); i++){
								controllers.get(i).resetState();
							}
							continue;
						}else if(input.equals("startrecording")){
							if(isRecording){
								System.out.println("You are already recording.");
								continue;
							}
							isRecording = true;
							record = "";
							System.out.println("Starting recording of your commands. Type 'stoprecording' end.");
							continue;
						}else if(input.equals("stoprecording")){
							if(!isRecording){
								System.out.println("Cannot stop recording. You never began recording.");
								continue;
							}
							isRecording = false;
							System.out.println("Stopping recording of your commands.");
							System.out.println("Type 'playrecording' to execute the recording, or 'saverecording' to save the recording as a file");
							continue;
						}else if(input.equals("playrecording")){
							if(isRecording){
								System.out.println("Warning: you are still recording.");
							}
							if(record.equals("")){
								System.out.println("The recording doesnt contain any commands!");
								continue;
							}
							playingRecoring = true;
							input = record;
						}else if(input.equals("saverecording")){
							if(isRecording){
								System.out.println("Warning: you are still recording.");
							}
							if(record.equals("")){
								System.out.println("The recording doesnt contain any commands!");
								continue;
							}
							System.out.print("Enter filename:");
							String filename = scanner.nextLine();
							FinchProgramFlow flow = parser.Parse(record);
							flow.addController(controllers);
							try{
								writeFile(flow.toCode(), filename);
								System.out.println("File written successfuly.");
							}catch(Exception e){
								System.out.println(e.getMessage());
							}
							continue;
						}else if(input.equals("")){
							continue;
						}
						FinchProgramFlow flow = parser.Parse(input);
						if(flow != null){
							flow.addController(controllers);
							if(isRecording && !playingRecoring){
								record += input+"\n";
							}
							flow.addLogger(logger);
							flow.execute();
						}else{
							System.out.println("Last command not executed.");
						}
					}
				}else if(option == 2){
					System.out.print("Enter filename:");
					Scanner scanner = new Scanner(System.in);
					FinchParser parser = new FinchParser();
					FinchExecutionLogger logger = new SystemOutExecutionLogger();
					((SystemOutExecutionLogger)logger).setPrefix("  ");
					String input = scanner.nextLine();
					String programBuffer = "";
					try {
					    BufferedReader in = new BufferedReader(findFile(input));
					    String str;
					    while ((str = in.readLine()) != null) {
					        programBuffer += str+"\n";
					    }
					    in.close();
					}catch(FileNotFoundException e){
						System.out.println(e.getMessage());
						continue;
					}catch (IOException e) {
						System.out.println("Error reading file.");
						continue;
					}
					FinchProgramFlow flow = parser.Parse(programBuffer);
					if(flow != null){
						flow.addController(controllers);
						flow.addLogger(logger);
						flow.execute();
					}else{
						System.out.println("Error executing program.");
					}
					System.out.println("Completed program successfuly!");
					System.out.println();
				}
			}else{
				System.out.println("\nUnrecgonized option.\n");
			}
		}

	}
	protected static void printActions(){
		System.out.println("available finch commands:");
		System.out.println("-------------------------");
		Class[] actions = ClassFinder.findActions();
		for(int i=0; i<actions.length; i++){
			Method m;
			try {
				m = actions[i].getMethod("syntax", null);
				String command = (String)m.invoke(null, new Object[0]);
				System.out.println(" -> "+command.replaceAll("(\n)", "$1    "));
			} catch (Exception e) {
				
			}	
		}
		System.out.println();
		System.out.println("available system commands");
		System.out.println("-------------------------");
		System.out.println(" -> reset          - resets the state of the finch to be facing north and at position (0,0)");
		System.out.println(" -> startrecording - begins recording any successful finch commands you issue.");
		System.out.println(" -> stoprecording  - stops a recording that was started with startrecording");
		System.out.println(" -> playrecording  - plays the recording created by start/stoprecording");
		System.out.println(" -> saverecording  - saves the recording to a file");
		System.out.println();
		
	}
	protected static ArrayList<FinchController> loadControllers() throws Exception{
		ArrayList<FinchController> controllers = new ArrayList<FinchController>();
		
		Configuration config = Configuration.getInstance();
		String[] ctrls = config.config.getStringArray("finchcontrollers.controller");
		for(int i=0; i < ctrls.length; i++){
			Class controlClass = Class.forName(ctrls[i]);
			Constructor construct = controlClass.getConstructor(null);
			FinchController cInstance = (FinchController)construct.newInstance(null);
			controllers.add(cInstance);
		}
		return controllers;
	}
	
	protected static void writeFile(String content, String filename) throws Exception{
		try{
			writeContent(content, filename);
		}catch(Exception e){
			throw new Exception("Error writing file ["+filename+"]: "+e.getMessage());
		}
	}
	protected static void writeContent(String content, String filename) throws IOException{
		File file = new File(filename);
		boolean exist = file.createNewFile();
		if (!exist){
			//System.out.println("File already exists.");
			//System.exit(0);
			ReadWriteTextFile.setContents(new File(filename), content);
		}else{
			FileWriter fstream = new FileWriter(filename);
			BufferedWriter out = new BufferedWriter(fstream);
			out.write(content);
			out.close();
			//System.out.println("File created successfully.");
		}
		
	}
	protected static FileReader findFile(String path) throws FileNotFoundException{
		ArrayList<String> includepath = getIncludePaths();
		includepath.add(0, "");
		
		int currPath = 0;
		FileReader fr;
		while(currPath < includepath.size()){
			try{
				fr = getFile(includepath.get(currPath)+path);
				return fr;
			}catch(FileNotFoundException e){
				currPath++;
				continue;
			}
		}
		throw new FileNotFoundException("Could not find file '"+path+"'");
	}
	protected static FileReader getFile(String path) throws FileNotFoundException{
		//System.out.println("attempting to open '"+path+"'");
		FileReader r = null;
		try {
			r = new FileReader(URLDecoder.decode(path, "UTF-8"));
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		}
		return r;
	}
	protected static ArrayList<String> getIncludePaths(){
		ArrayList<String> includepaths = new ArrayList<String>();
		Configuration config;
		try{
			config = Configuration.getInstance();
		}catch(Exception e){
			return includepaths;
		}
		String appPath = getApplicationPath();//.replaceAll("\\", "\\\\");
		if(appPath.endsWith("/")){
			appPath = appPath.substring(0, appPath.length()-1);
		}
		if(appPath.startsWith("/")){
			appPath = appPath.substring(1);
		}
		String[] paths = config.config.getStringArray("includepaths.path");
		for(int i=0; i < paths.length; i++){
			
			String path = paths[i].replaceAll("\\$\\{finchpath\\}", Matcher.quoteReplacement(appPath));
			includepaths.add(path);
		}
		return includepaths;
	}
	protected static String getApplicationPath(){
		String applicationDir = FinchParserPrototype.class.getProtectionDomain().getCodeSource().getLocation().getPath(); 
		return applicationDir;
		/*
		if (applicationDir.endsWith(".exe")){
		    applicationDir = new File(applicationDir).getParent();
		}else{
		    // Add the path to the class files  
		    applicationDir += FinchParserPrototype.class.getName().replace('.', '/');

		    // Step one level up as we are only interested in the 
		    // directory containing the class files
		    applicationDir = new File(applicationDir).getParent();
		}
		return applicationDir;
		*/
	}
}






